package com.liuwa.openapi.service;

import com.liuwa.common.constant.Constants;
import com.liuwa.common.core.redis.RedisCache;
import com.liuwa.common.utils.DateUtils;
import com.liuwa.common.utils.Encrypt;
import com.liuwa.common.utils.RequestUtils;
import com.liuwa.common.utils.StringUtils;
import com.liuwa.common.utils.sign.Md5Utils;
import com.liuwa.framework.manager.RedisManager;
import com.liuwa.framework.security.authens.OauthAccessToken;
import com.liuwa.knowledge.domain.User;
import org.bouncycastle.jcajce.provider.digest.SHA256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service
public class OpenapiTokenService {


    /**
     * 登录用户 redis key
     */
    public static final String OPENAPI_TOKEN_KEY = "openapi_tokens:";

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedisCache redisCache;



    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期（默认30分钟）
    @Value("${token.expireTime}")
    private int expireTime;

    // token 加密算法
    @Value("${token.algorithm:md5}")
    private String algorithm;

    /**
     * 生成
     * @param user
     * @return
     */
    public OauthAccessToken generate(User user){
        int validDays = 60;
        String token = (Encrypt.md5(user.getUsername(), user.getSalt()) + Encrypt.SHA256(StringUtils.leftPad(String.valueOf(user.getUserId()), 8, "0"))).toUpperCase();
        OauthAccessToken accessToken = new OauthAccessToken();
        accessToken.setAccessToken(token);
        accessToken.setAccessToken(token);
        accessToken.setServerTime(new Date());
        accessToken.setExpiresAt(DateUtils.addDays(accessToken.getServerTime(), validDays));
        accessToken.setUserId(user.getUserId());
        //accessToken.setRefreshToken(token);
        String macKey = Encrypt.shortMd5(token);
        accessToken.setMacKey(macKey);
        redisCache.setCacheObject(OPENAPI_TOKEN_KEY + token, user, validDays, TimeUnit.DAYS);
        return accessToken;
    }

    /**
     * 获取登录用户
     * @return
     */
    public User getLoginUser(){
        String authorization = RequestUtils.getHeader(header);
        String token = verifyAuthorization(authorization, RequestUtils.getRequest());
        User  user = redisCache.getCacheObject(OPENAPI_TOKEN_KEY + token);
        return user;
    }

    /**
     * 校验授权头
     * @param authorization
     */
    private String verifyAuthorization(String authorization, HttpServletRequest request){
        if(StringUtils.isEmpty(authorization)){
            return "";
        }

        String authenticationValue = authorization.substring(Constants.MAC_TOKEN_PREFIX.length());
        String host = request.getHeader("Host");
        if (StringUtils.isEmpty(host)) {
            try {
                host = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                throw new InsufficientAuthenticationException("Host 获取失败");
            }
        }

        if(StringUtils.isNotEmpty(Constants.HOST_REPLACE)){
            String[] arr = Constants.HOST_REPLACE.split("=>");
            if(arr.length == 2){
                if(host.equals(arr[0].trim())){
                    host = arr[1].trim();
                }
            }
        }

        String requestURI = urlHandler(request);
        requestURI = getURI(host, request.getServerPort(), requestURI);

        String[] tokenSlips = authenticationValue.split(",");
        if(tokenSlips.length<3){
            throw new InsufficientAuthenticationException(null);
        }
        String id = tokenSlips[0].substring(tokenSlips[0].indexOf("=") + 1).replace("\"", "").trim();
        String nonce = tokenSlips[1].substring(tokenSlips[1].indexOf("=") + 1).replace("\"", "").trim();
        String mac = tokenSlips[2].substring(tokenSlips[2].indexOf("=") + 1).replace("\"", "").trim();
        if(tokenSlips.length > 3){
            String appId = tokenSlips[3].substring(tokenSlips[3].indexOf("=") + 1).replace("\"", "").trim();
            logger.info("appId:{}", appId);
        }

        checkMac(id, nonce, mac, host, request.getMethod(), requestURI);

        return id;

    }

    /**
     * mac 校验
     * @param token
     * @param nonce
     * @param mac
     * @param host
     * @param method
     * @param requestURI
     */
    private void checkMac(String token, String nonce, String mac, String host, String method, String requestURI){
        String key = "token:nonce:" + nonce;
        if(RedisManager.exists(key)){
            throw new InsufficientAuthenticationException("当前nonce已使用");
        }

        long timestamp = Long.parseLong(nonce.split(":")[0]);
        // 大于5分钟秒
        if(Math.abs(timestamp - System.currentTimeMillis()) > 300000){
            logger.error("时间戳异常：{} <==> {}", timestamp, System.currentTimeMillis());
            throw new InsufficientAuthenticationException("时间戳超过有效时间");
        }

        requestURI = requestURI.replace("%2F", "/");

        String macKey = Encrypt.shortMd5(token);


        StringBuilder sbRawMac = new StringBuilder();
        sbRawMac.append(nonce);
        sbRawMac.append("\n");
        sbRawMac.append(method.toUpperCase());
        sbRawMac.append("\n");
        sbRawMac.append(requestURI);
        sbRawMac.append("\n");
        sbRawMac.append(host);
        sbRawMac.append("\n");


        if(!"md5".equalsIgnoreCase(algorithm) && !"hmac256".equalsIgnoreCase(algorithm)){
            throw new InsufficientAuthenticationException("请配置好签名算法");
        }

        boolean verify = true;
        if("md5".equalsIgnoreCase(algorithm) && !mac.equalsIgnoreCase(md5(sbRawMac.toString(), macKey))){
            verify = false;
        }
        else if("hmac256".equalsIgnoreCase(algorithm) && !mac.equalsIgnoreCase(encryptHMac256(sbRawMac.toString(), macKey))){
            verify = false;
        }

        if(!verify){
            logger.error(mac + ":" + sbRawMac.toString());
            throw new InsufficientAuthenticationException("授权校验失败");
        }
    }

    /**
     * 处理代理请求url
     * @param request
     * @return
     */
    private static String urlHandler(HttpServletRequest request) {
        StringBuffer requestURL = request.getRequestURL();
        String reqString = requestURL.toString();
        String queryStr = request.getQueryString();
        // 判断请求参数是否为空
        if (!org.springframework.util.StringUtils.isEmpty(queryStr)) {
            if (queryStr.indexOf("&")>-1) {
                String [] paramString = queryStr.split("&");
                requestURL.append("?");// 参数
                for (String string : paramString) {
                    if (string.indexOf(Constants.PROXY_PARAM)>-1) {
                        continue;
                    }
                    requestURL.append(string).append("&");// 参数
                }
                if (requestURL.lastIndexOf("&")>-1) {
                    reqString = requestURL.substring(0, requestURL.length()-1).toString();
                }
            }else {
                if (queryStr.indexOf(Constants.PROXY_PARAM)==-1) {
                    reqString = requestURL.append("?").append(queryStr).toString();// 参数
                }
            }
        }
        return reqString;
    }


    /**
     * 获取uri
     * @param host
     * @param port
     * @param url
     * @return
     */
    private static String getURI(String host, Integer port, String url) {
        if (host == "") {
            return "";
        }

        int index = url.indexOf(host);
        if (index == -1) {
            return "";
        }

        url =  url.substring(index+host.length());
        String portKey = ":" + port;
        if(url.startsWith(portKey)){
            url = url.substring(portKey.length());
        }
        return url;
    }


    /**
     * HMac256 加密
     * @param content
     * @param key
     * @return
     */
    private static String encryptHMac256(String content, String key) {
        Assert.notNull(content, "content");
        Assert.notNull(key, "key");
        // 还原密钥
        SecretKey secretKey = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        // 实例化Mac
        Mac mac = null;
        try {
            mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 执行消息摘要
        byte[] digest = mac.doFinal(content.getBytes());
        return new String(Base64.encode(digest));
    }

    /**
     * MD5加密
     * @param content
     * @param key
     * @return
     */
    private static String md5(String content, String key) {

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            if (key != null && !"".equals(key)) {
                content =  content + "{" + key.toString() + "}";
            }
            byte[] digest = messageDigest.digest(content.getBytes("utf-8"));
            return new String(Hex.encode(digest));
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;

    }
}
